/*
	* Created by: Varun Tyagi
	*
	* This tutorial illustrates how animation works.
	* The basic idea for animation is to take a series of images in one sprite sheet and
	* show them one after another to create an illusion of animation. 
*/

/* The header files. */
#include "SDL.h"
#include "SDL_image.h"
#include <string>

/* Screen Attributes. */
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

/* Frames per second. */
const int FRAMES_PER_SECOND = 30;

/* The dimensions of the entity. */
const int FOO_WIDTH = 64;
const int FOO_HEIGHT = 205;

/* The direction status of the stick figure. */
const int FOO_RIGHT = 1;
const int FOO_LEFT = 0;

/* The surface. */
SDL_Surface *pFoo = nullptr;
SDL_Surface *pScreen = nullptr;

/* The event structure. */
SDL_Event event;

/* The areas of the sprite sheet. */
SDL_Rect clipsRight[4];
SDL_Rect clipsLeft[4];

/* The entity class. */
class Foo
{
private:

	/* Offset. Since right now we are only moving the entity left and right, we will use only x offset. */
	int m_iOffsetX;

	/* Rate of movement. */
	int m_iVeclocityX;
	
	/* The current frame. */
	int m_iFrame;

	/* Animation status */
	int m_iAnimStatus;

public:

	/* Initialize. */
	Foo();

	/* Input Handler. */
	void handle_input( void );

	/* Move the entity. */
	void move_entity( void );

	/* Show the entity. */
	void show_entity( void );
};

/* Timer class. */
class Timer
{
private:

	/* The time on which the timer was started. */
	int m_iStartTime;

	/* The pause tick when the timer was paused. */
	int m_iPauseTime;

	/* Status of the timer. */
	bool m_bStart;
	bool m_bPaused;

public:

	/* Initialize. */
	Timer();

	/* The timer actions. */
	void start( void );
	void stop( void );
	void pause( void );
	void unpause( void );

	/* Get the timer ticks. */
	int get_ticks( void );
	
	/* Get the status of the timer. */
	bool has_started( void );
	bool is_paused( void );
};

/* Image loader. */
SDL_Surface *load_image( std::string filename )
{
	/* The loaded image pointer. */
	SDL_Surface* pLoadedImage = nullptr;

	/* The Optimized image. */
	SDL_Surface* pOptimizedImage = nullptr;

	/* Load the image. */
	pLoadedImage = IMG_Load( filename.c_str() );

	/* Check if the file has been loaded properly. */
	if( nullptr != pLoadedImage )
	{
		/* Optimize the image. */
		pOptimizedImage = SDL_DisplayFormat( pLoadedImage );

		/* Check if the image has been optimized correctly. */
		if( nullptr != pOptimizedImage )
		{
			/* Color key surface. */
			SDL_SetColorKey( pOptimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( pOptimizedImage->format, 0, 0xFF, 0xFF ) );
		}
	}

	/* Return the optimized image. */
	return pOptimizedImage;
}

/* Apply the surface. */
void apply_surface( int x, int y, SDL_Surface* pSource, SDL_Surface* pDestination, SDL_Rect* rects = nullptr )
{
	/* Offset. */
	SDL_Rect offset;

	/* get the offset. */
	offset.x = x;
	offset.y = y;

	/* Blit the surface. */
	SDL_BlitSurface( pSource, rects, pDestination, &offset );
}

/* Set the animation clips. */
void set_clips( void )
{
	/* Clip the sprites. */
	clipsRight[0].x = 0;
	clipsRight[0].y = 0;
	clipsRight[0].w = FOO_WIDTH;
	clipsRight[0].h = FOO_HEIGHT;

	clipsRight[1].x = FOO_WIDTH;
	clipsRight[1].y = 0;
	clipsRight[1].w = FOO_WIDTH;
	clipsRight[1].h = FOO_HEIGHT;

	clipsRight[2].x = FOO_WIDTH * 2;
	clipsRight[2].y = 0;
	clipsRight[2].w = FOO_WIDTH;
	clipsRight[2].h = FOO_HEIGHT;

	clipsRight[3].x = FOO_WIDTH * 3;
	clipsRight[3].y = 0;
	clipsRight[3].w = FOO_WIDTH;
	clipsRight[3].h = FOO_HEIGHT;

	clipsLeft[0].x = 0;
	clipsLeft[0].y = FOO_HEIGHT;
	clipsLeft[0].w = FOO_WIDTH;
	clipsLeft[0].h = FOO_HEIGHT;

	clipsLeft[1].x = FOO_WIDTH;
	clipsLeft[1].y = FOO_HEIGHT;
	clipsLeft[1].w = FOO_WIDTH;
	clipsLeft[1].h = FOO_HEIGHT;

	clipsLeft[2].x = FOO_WIDTH * 2;
	clipsLeft[2].y = FOO_HEIGHT;
	clipsLeft[2].w = FOO_WIDTH;
	clipsLeft[2].h = FOO_HEIGHT;

	clipsLeft[3].x = FOO_WIDTH * 3;
	clipsLeft[3].y = FOO_HEIGHT;
	clipsLeft[3].w = FOO_WIDTH;
	clipsLeft[3].h = FOO_HEIGHT;	
}

/* Initialize SDL. */
bool init_SDL( void )
{
	/* Initialize all the subsystems. */
	if( -1 == SDL_Init( SDL_INIT_EVERYTHING ) )
	{
		return false;
	}

	/* Set up the screen. */
	pScreen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );

	/* Check the screen. */
	if( nullptr == pScreen )
	{
		return false;
	}

	/* Set window caption. */
	SDL_WM_SetCaption( "Animation", nullptr );

	/* Everything went well. */
	return true;
}

/* Load files. */
bool load_Files( void )
{
	/* Load the sprite sheet. */
	pFoo = load_image( "foo.png" );

	/* If there is a problem loading the image. */
	if( nullptr == pFoo )
	{
		return false;
	}

	/* Else everything went just fine. */
	return true;
}

/* Clean SDL. */
void clean_SDL( void )
{
	/* Free FOO. */
	SDL_FreeSurface( pFoo );

	/* QUIT SDL. */
	SDL_Quit();
}

/*********** FOO *************/
Foo::Foo()
{
	/* Initialize the member variables. */
	m_iOffsetX = 0;
	m_iVeclocityX = 0;

	/* Animation variables. */
	m_iFrame = 0;
	m_iAnimStatus = FOO_RIGHT;
}

/* Event handler. */
void Foo::handle_input( void )
{
	/* If the key is pressed down. */
	if( SDL_KEYDOWN == event.type )
	{
		/* Set the velocity of the entity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_RIGHT:
		case SDLK_KP6:
			{
				m_iVeclocityX += FOO_WIDTH / 4;
				break;
			}
		case SDLK_LEFT:
		case SDLK_KP4:
			{
				m_iVeclocityX -= FOO_WIDTH / 4;
				break;
			}
		}
	}

	/* Else if the key is released. */
	else if( SDL_KEYUP == event.type )
	{
		/* Set the reverse velocity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_RIGHT:
		case SDLK_KP6:
			{
				m_iVeclocityX -= FOO_WIDTH / 4;
				break;
			}
		case SDLK_LEFT:
		case SDLK_KP4:
			{
				m_iVeclocityX += FOO_WIDTH / 4;
				break;
			}
		}
	}
}

/* Moving the entity. */
void Foo::move_entity( void )
{
	/* Move the offset right. */
	m_iOffsetX += m_iVeclocityX;

	/* Keep track to see if the entity has reached the end of the screen. */
	if( ( m_iOffsetX < 0 ) || ( m_iOffsetX + FOO_WIDTH > SCREEN_WIDTH ) )
	{
		/* Reverse the entity. */
		m_iOffsetX -= m_iVeclocityX;
	}
}

/* Show the entity. */
void Foo::show_entity( void )
{
	/* If Foo is moving towards left. */
	if( 0 > m_iVeclocityX )
	{
		/* Set the animation status. */
		m_iAnimStatus = FOO_LEFT;

		/* Move to the next frame. */
		++m_iFrame;
	}
	/* If Foo is moving towards right. */
	else if( m_iVeclocityX > 0 )
	{
		/* Set the animation status. */
		m_iAnimStatus = FOO_RIGHT;

		/* Move to the next frame. */
		++m_iFrame;

	}
	/* Or, if Foo is not moving at all. */
	else
	{
		/* Reset the frame count. */
		m_iFrame = 0;
	}

	/* Loop the animation. */
	if( 4 <= m_iFrame )
	{
		m_iFrame = 0;
	}

	/* Show FOO. */
	if( FOO_RIGHT == m_iAnimStatus )
	{
		apply_surface( m_iOffsetX, SCREEN_HEIGHT - FOO_HEIGHT, pFoo, pScreen, &clipsRight[ m_iFrame ] );
	}
	else if( FOO_LEFT ==  m_iAnimStatus )
	{
		apply_surface( m_iOffsetX, SCREEN_HEIGHT - FOO_HEIGHT, pFoo, pScreen, &clipsLeft[ m_iFrame ] );
	}
}

/*********** TIMER ************/
Timer::Timer()
{
	/* Initialize the variables. */
	m_iStartTime = 0;
	m_iPauseTime = 0;

	m_bStart = false;
	m_bPaused = false;
}

void Timer::start( void )
{
	/* Start the timer. */
	m_bStart = true;

	m_bPaused = false;

	/* Get the starting tick. */
	m_iStartTime = SDL_GetTicks();
}

void Timer::stop( void )
{
	/* Stop the timer. */
	m_bStart = false;

	m_bPaused =  false;
}

void Timer::pause( void )
{
	/* If the timer is already running and in not paused. */
	if( ( true == m_bStart ) && ( false == m_bPaused ) )
	{
		/* Pause the timer. */
		m_bPaused = true;

		/* Calculate the ticks. */
		m_iPauseTime = SDL_GetTicks() - m_iStartTime;
	}
}

void Timer::unpause( void )
{
	/* If the timer is paused. */
	if( true == m_bPaused )
	{
		/* Unpause the timer. */
		m_bPaused = false;

		/* Reset the starting tick. */
		m_iStartTime = SDL_GetTicks() - m_iPauseTime;

		/* Reset the pause time. */
		m_iPauseTime = 0;
	}
}

/* Get ticks. */
int Timer::get_ticks( void )
{
	/* If the timer is running. */
	if( true == m_bStart )
	{
		/* If the timer is paused. */
		if( true == m_bPaused )
		{
			/* Return the pause tick, simply. */
			return m_iPauseTime;
		}
		else
		{
			/* Return the current time. */
			return ( SDL_GetTicks() - m_iStartTime );
		}
	}

	/* If the timer is not running then return 0. */
	return 0;
}

bool Timer::has_started( void )
{
	return m_bStart;
}

bool Timer::is_paused( void )
{
	return m_bPaused;
}


/* Main Function. */
int main( int argc, char** argv )
{
	/* QUIT flag. */
	bool bQuit = false;

	/* Initialize. */
	if( false == init_SDL() )
	{
		return 1;
	}

	/* Load the files. */
	if( false == load_Files() )
	{
		return 1;
	}

	/* Clip the sprite sheet. */
	set_clips();

	/* Frame counter. */
	Timer fps;

	/* Stick figure. */
	Foo walk;

	/* While the user has not quit. */
	while( false == bQuit )
	{
		/* Start the frame timer. */
		fps.start();

		/* While there is event to handle. */
		while( SDL_PollEvent( &event ) ) 
		{
			/* Handle the events of foo. */
			walk.handle_input();

			/* If the user has Xed the window. */
			if( SDL_QUIT == event.type )
			{
				/* QUIT. */
				bQuit = true;
			}
		}

		/* Move the stick. */
		walk.move_entity();

		/* Fill with white color. */
		SDL_FillRect( pScreen, &pScreen->clip_rect, SDL_MapRGB( pScreen->format, 0xFF, 0xFF, 0xFf ) );

		/* Show the stick on the screen. */
		walk.show_entity();

		/* Update the screen. */
		if( -1 == SDL_Flip( pScreen ) )
		{
			return 1;
		}

		/* Cap the frame rate. */
		if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
		{
			/* Delay. */
			SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
		}
	}

	/* Clean SDL. */
	clean_SDL();

	return 0;
}